手把手教你用Django搭建博客(三)
作者:杨学光
博客:http://zmrenwu.com/
5、让 Django 完成翻译:迁移数据库
我们已经编写了博客数据库模型的代码,但那还只是 Python 代码而已,Django 还没有把它翻译成数据库语言,因此实际上这些数据库表还没有真正的在数据库中创建。
迁移数据库
为了让 Django 完成翻译,创建好这些数据库表,我们再一次请出我的工程管理助手 manage.py。激活虚拟环境,切换到 manage.py 文件所在的目录下,分别运行 python manage.py makemigrations
和python manage.py migrate
命令:
C:\WINDOWS\system32>C:\Users\yangxg\Envs\blogproject_env\Scripts\activate (blogproject_env) C:\WINDOWS\system32>cd C:\Users\yangxg\Workspace\blogproject (blogproject_env) C:\Users\yangxg\Workspace\blogproject>python manage.py makemigrations Migrations for 'blog': blog\migrations\0001_initial.py: - Create model Category - Create model Post - Create model Tag - Add field tags to post (blogproject_env) C:\Users\yangxg\Workspace\blogproject>python manage.py migrate Operations to perform: Apply all migrations: admin, auth, blog, contenttypes, sessions Running migrations: Applying contenttypes.0001_initial... OK Applying auth.0001_initial... OK Applying admin.0001_initial... OK Applying admin.0002_logentry_remove_auto_add... OK Applying contenttypes.0002_remove_content_type_name... OK Applying auth.0002_alter_permission_name_max_length... OK Applying auth.0003_alter_user_email_max_length... OK Applying auth.0004_alter_user_username_opts... OK Applying auth.0005_alter_user_last_login_null... OK Applying auth.0006_require_contenttypes_0002... OK Applying auth.0007_alter_validators_add_error_messages... OK Applying auth.0008_alter_user_username_max_length... OK Applying blog.0001_initial... OK Applying sessions.0001_initial... OK
注意:如果代码中含有中文注释,且你使用的是 Python 2 开发环境的话,会得到一个编码错误。因此请在含有中文注释的文件最开始处加入编码声明:# coding: utf-8。
当我们执行了 python manage.py makemigrations
后,Django 在 blog 应用的 migrations\ 目录下生成了一个 0001_initial.py 文件,这个文件是 Django 用来记录我们对模型做了哪些修改的文件。目前来说,我们在 models.py 文件里创建了 3 个模型类,Django 把这些变化记录在了 0001_initial.py 里。
不过此时还只是告诉了 Django 我们做了哪些改变,为了让 Django 真正地为我们创建数据库表,接下来又执行了 python manage.py migrate
命令。Django 通过检测应用中 migrations\ 目录下的文件,得知我们对数据库做了哪些操作,然后它把这些操作翻译成数据库操作语言,从而把这些操作作用于真正的数据库。
你可以看到命令的输出除了 Applying blog.0001_initial... OK 外,Django 还对其它文件做了操作。这是因为除了我们自己建立的 blog 应用外,Django 自身还内置了很多应用,这些应用本身也是需要存储数据的。可以在 settings.py 的 INSTALLED_APP
设置里看到这些应用,当然我们目前不必关心这些。
python blogproject/settings.py INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'blog', ]
对于了解数据库语言的人,你可以运行下面的命令看看 Django 究竟为我们做了什么:
python manage.py sqlmigrate blog 0001
你将看到输出了经 Django 翻译后的数据库表创建语句,这有助于你理解 Django ORM 的工作机制。
选择数据库版本
我们没有安装任何的数据库软件,Django 就帮我们迁移了数据库。这是因为我们使用了 Python 内置的 SQLite3 数据库。
SQLite3 是一个十分轻巧的数据库,它仅有一个文件。你可以看一到项目根目录下多出了一个 db.sqlite3 的文件,这就是 SQLite3 数据库文件,Django 博客的数据都会保存在这个数据库文件里。
Django 在 settings.py 里为我们做了一些默认的数据库配置:
python blogproject/settings.py ## 其它配置选项... DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), } } ## 其它配置选项...
可以看到默认的数据库引擎就是使用的 SQLite3。
当然一些人倾向于使用 MySQL 等大型数据库,至于 Django 如何配置 MySQL 这里就不赘述了,你可以自行使用搜索引擎或者查阅 Django 的官方文档解决。对于一个小型博客而言,SQLite3 数据库足以胜任。
用 Django 的方式操作数据库
数据库最主要的操作就是往里面存入数据、从中取出数据、修改已保存的数据和删除不再需要的数据。和创建数据库表一样,Django 为这些操作提供了一整套方法,从而把我们从数据库语言中解放出来。我们不用学习如何利用数据库语言去完成这些操作,只要简单地调用几个 Python 函数就可以满足我们的需求。
存数据
先在命令行中来探索一下这些函数,感受一下如何用 Django 的方式来操作数据库。在 manage.py 所在目录下运行 python manage.py shell
命令:
(blogproject_env) C:\Users\yangxg\Workspace\blogproject>python manage.py shell Python 3.5.2 (v3.5.2:4def2a2901a5, Jun 25 2016, 22:18:55) [MSC v.1900 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information. (InteractiveConsole) >>>
这打开了一个交互式命令行。
首先我们来创建一个分类和一个标签:
python >>> from blog.models import Category, Tag, Post >>> c = Category(name='category test') >>> c.save() >>> t = Tag(name='tag test') >>> t.save()
我们首先导入 3 个之前写好的模型类,然后实例化了一个 Category
类和一个 Tag
类,为他们的属性 name
赋了值。为了让 Django 把这些数据保存进数据库,调用实例的 save
方法即可。
再创建一篇文章试试,但创建文章之前,我们需要先创建一个 User,用于指定文章的作者。创建 User 的命令 Django 已经帮我们写好了,依然是通过 manage.py 来运行。首先按住 Ctrl + c 退出命令交互栏(一次退不出就连续多按几次),运行 python manage.py createsuperuser
命令并根据提示创建用户:
(blogproject_env) C:\Users\yangxg\Workspace\blogproject>python manage.py createsuperuser Username (leave blank to use 'zmrenwu@163.com'): myuser Email address: a@aa.com Password: Password (again): Superuser created successfully.
运行 python manage.py createsuperuser
开始创建用户,之后会提示你输入用户名、邮箱、密码和确认密码,按照提示输入即可。注意一点的是密码输入过程中不会有任何字符显示,不要误以为你的键盘出问题了,正常输入即可。最后出现 Superuser created successfully. 说明用户创建成功了。
再次运行 python manage.py shell
进入 Python 命令交互栏,开始创建文章:
python >>> from blog.models import Category, Tag, Post >>> from django.utils import timezone >>> from django.contrib.auth.models import User >>> user = User.objects.get(username='myuser') >>> c = Category.objects.get(name='category test') >>> p = Post(title='title test', body='body test', created_time=timezone.now(), modified_time=timezone.now(), category=c, author=user) >>> p.save()
由于我们重启了 shell,因此需要重新导入了 Category
、Tag
、Post
以及 User
。我们还导入了一个 Django 提供的辅助模块 timezone,这是因为我们需要调用它的 now()
方法为 created_time
和 modified_time
指定时间,容易理解 now
方法返回当前时间。然后我们根据用户名和分类名,通过 get
方法取出了存在数据库中的 User
和 Category
(取数据的方法将在下面介绍)。接着我们为文章指定了 title
、body
、created_time
、modified_time
值,并把它和前面创建的 Category 以及 User 关联了起来。允许为空 excerpt
、tags
我们就没有为它们指定值了。
注意:我们这里使用 get
方法根据 Category
的 name
属性的值获取分类的一条记录。Category.objects.get(name='category test')
的含义是从数据库中取出 name
的值为 category test 的分类记录。确保数据库中只有一条值为 category test 的记录,否则 get
方法将返回一个 MultipleObjectsReturned
异常。如果你不小心已经存了多条记录,请删掉多余的记录。如何删除数据请看下文。
取数据
数据已经存入数据库了,现在要把它们取出来看看:
python >>> Category.objects.all() <QuerySet [<Category: Category object>]> >>> Tag.objects.all() <QuerySet [<Tag: Tag object>]> >>> Post.objects.all() <QuerySet [<Post: Post object>]> >>>
objects
是我们的模型管理器,它为我们提供一系列从数据库中取数据方法,这里我们使用了 all
方法,表示我们要把对应的数据全部取出来。可以看到 all
方法都返回了数据,这些数据应该是我们之前存进去的,但是显示的字符串有点奇怪,无法看出究竟是不是我们之前存入的数据。为了让显示出来的数据更加人性化一点,我们为 3 个模型分别增加一个 __str__
方法:
python blog/models.py from django.utils.six import python_2_unicode_compatible # python_2_unicode_compatible 装饰器用于兼容 Python2 @python_2_unicode_compatible class Category(models.Model): ... def __str__(self): return self.name @python_2_unicode_compatible class Tag(models.Model): ... def __str__(self): return self.name @python_2_unicode_compatible class Post(models.Model): ... def __str__(self): return self.title
定义好 __str__
方法后,解释器显示的内容将会是 __str__
方法返回的内容。这里 Category
返回分类名 name
,Tag
返回标签名,而 Post
返回它的 title
。
python_2_unicode_compatible
装饰器用于兼容 Python2。如果你使用的 Python3 开发环境,去掉这个装饰器不会有任何影响。如果你使用的 Python2 开发环境,而又不想使用这个装饰器,则将 __str__
方法改为__unicode__
方法即可。
先按 Ctrl + c 退出 Shell,再重新运行 python manage.py shell
进入 Shell。
python >>> from blog.models import Category, Tag, Post >>> Category.objects.all() <QuerySet [<Category: category test>]> >>> Tag.objects.all() <QuerySet [<Tag: tag test>]> >>> Post.objects.all() <QuerySet [<Post: title test>]> >>> Post.objects.get(title='title test') <Post: title test>
可以看到返回的是我们之前存入的数据。
此外我们在创建文章时提到了通过 get
方法来获取数据,这里 all
方法和 get
方法的区别是:all
方法返回全部数据,是一个类似于列表的数据结构(QuerySet);而 get
返回一条记录数据,如有多条记录或者没有记录,get
方法均会抛出相应异常。
改数据
尝试修改数据:
python >>> c = Category.objects.get(name='category test') >>> c.name = 'category test new' >>> c.save() >>> Category.objects.all() <QuerySet [<Category: test category new>]>
首先通过 get
方法根据分类名 name
获取值为 category test 到分类,修改它的 name
属性为新的值 category test new,然后调用 save
方法把修改保存到数据库,之后可以看到数据库返回的数据已经是修改后的值了。Tag
、Post
的修改也一样。
删数据
删除掉数据:
python >>> p = Post.objects.get(title='title test') >>> p <Post: title test> >>> p.delete() (1, {'blog.Post_tags': 0, 'blog.Post': 1}) >>> Post.objects.all() <QuerySet []>
先根据标题 title
的值从数据库中取出 Post
,保存在变量 p
中,然后调用它的delete
方法,最后看到 Post.objects.all()
返回了一个空的 QuerySet(类似于一个列表),表明数据库中已经没有 Post,Post 已经被删除了。
这就是 Django 对数据库增、删、改、查的操作。除了上述演示的方法外,Django 还为我们提供了大量其它的方法,这些方法有一部分会在教程中使用,用到时我会讲解它们的用法。但以后你开发自己的项目时,你就需要通过阅读 Django 的官方文档(https://docs.djangoproject.com/en/1.10/ref/models/querysets/) 来了解有哪些方法可用以及如何使用它们。
总结
本章节的代码位于:Step4: make migrations and migrate(https://github.com/zmrenwu/django-blog-tutorial/tree/Step4_make-migrations-and-migrate)
如果遇到问题,请通过下面的方式寻求帮助。
在 让 Django 完成翻译:迁移数据库 - 追梦人物的博客(http://zmrenwu.com/post/6/) 的评论区留言。
将问题的详细描述通过邮件发送到 djangostudyteam@163.com,一般会在 24 小时内回复。
6、Django 博客首页视图
Django 处理 HTTP 请求
Web 应用的交互过程其实就是 HTTP 请求与响应的过程。无论是在 PC 端还是移动端,我们通常使用浏览器来上网,上网流程大致来说是这样的:
我们打开浏览器,在地址栏输入想访问的网址,比如 http://zmrenwu.com/(当然你也可能从收藏夹里直接打开网站,但本质上都是一样的)。
浏览器知道我们想要访问哪个网址后,它在后台帮我们做了很多事情。主要就是把我们的访问意图包装成一个 HTTP 请求,发给我们想要访问的网址所对应的服务器。通俗点说就是浏览器帮我们通知网站的服务器,说有人来访问你啦,访问的请求都写在 HTTP 里了,你按照要求处理后告诉我,我再帮你回应他!
服务器处理了HTTP 请求,然后生成一段 HTTP 响应给浏览器。浏览器解读这个响应,把相关的内容在浏览器里显示出来,于是我们就看到了网站的内容。比如你访问了我的博客主页 http://zmrenwu.com/,服务器接收到这个请求后就知道用户访问的是首页,首页显示的是全部文章列表,于是它从数据库里把文章数据取出来,生成一个写着这些数据的 HTML 文档,包装到 HTTP 响应里发给浏览器,浏览器解读这个响应,把 HTML 文档显示出来,我们就看到了文章列表的内容。
因此,Django 作为一个 Web 框架,它的使命就是处理流程中的第二步。即接收浏览器发来的 HTTP 请求,返回相应的 HTTP 响应。于是引出这么几个问题:
Django 如何接收 HTTP 请求?
Django 如何处理这个 HTTP 请求?
Django 如何生成 HTTP 响应?
对于如何处理这些问题,Django 有其一套规定的机制。我们按照 Django 的规定,就能开发出所需的功能。
Hello 视图函数
我们先以一个最简单的 Hello World 为例来看看 Django 处理上述问题的机制是怎么样的。
绑定 URL 与视图函数
首先 Django 需要知道当用户访问不同的网址时,应该如何处理这些不同的网址(即所说的路由)。Django 的做法是把不同的网址对应的处理函数写在一个 urls.py 文件里,当用户访问某个网址时,Django 就去会这个文件里找,如果找到这个网址,就会调用和它绑定在一起的处理函数(叫做视图函数)。
下面是具体的做法,首先在 blog 应用的目录下创建一个 urls.py 文件,这时你的目录看起来是这样:
blog\ __init__.py admin.py apps.py migrations\ 0001_initial.py __init__.py models.py tests.py views.py urls.py
在 blog\urls.py 中写入这些代码:
python blog/urls.py from django.conf.urls import url from . import views urlpatterns = [ url(r'^$', views.index, name='index'), ]
我们首先从 django.conf.urls 导入了 url
函数,又从当前目录下导入了 views 模块。然后我们把网址和处理函数的关系写在了 urlpatterns
列表里。
绑定关系的写法是把网址和对应的处理函数作为参数传给 url
函数(第一个参数是网址,第二个参数是处理函数),另外我们还传递了另外一个参数 name
,这个参数的值将作为处理函数 index
的别名,这在以后会用到。
注意这里我们的网址是用正则表达式写的,Django 会用这个正则表达式去匹配用户实际输入的网址,如果匹配成功,就会调用其后面的视图函数做相应的处理。
比如说我们本地开发服务器的域名是 http://127.0.0.1:8000,那么当用户输入网址 http://127.0.0.1:8000 后,Django 首先会把协议 http、域名 127.0.0.1 和端口号 8000 去掉,此时只剩下一个空字符串,而 r'^$'
的模式正是匹配一个空字符串(这个正则表达式的意思是以空字符串开头且以空字符串结尾),于是二者匹配,Django 便会调用其对应的 views.index
函数。
注意:在项目根目录的 blogproject\ 目录下(即 settings.py 所在的目录),原本就有一个 urls.py 文件,这是整个工程项目的 URL 配置文件。而我们这里新建了一个 urls.py 文件,且位于 blog 应用下。这个文件将用于 blog 应用相关的 URL 配置。不要把两个文件搞混了。
编写视图函数
第二步就是要实际编写我们的 views.index
视图函数了,按照惯例视图函数定义在 views.py 文件里:
python blog/views.py from django.http import HttpResponse def index(request): return HttpResponse("欢迎访问我的博客首页!")
我们前面说过,Web 服务器的作用就是接收来自用户的 HTTP 请求,根据请求内容作出相应的处理,并把处理结果包装成 HTTP 响应返回给用户。
这个两行的函数体现了这个过程。它首先接受了一个名为 request
的参数,这个request
就是 Django 为我们封装好的 HTTP 请求,它是类 HttpRequest
的一个实例。然后我们便直接返回了一个 HTTP 响应给用户,这个 HTTP 响应也是 Django 帮我们封装好的,它是类 HttpResponse
的一个实例,只是我们给它传了一个自定义的字符串参数。
浏览器接收到这个响应后就会在页面上显示出我们传递的内容 :欢迎访问我的博客首页!
配置项目 URL
还差最后一步了,我们前面建立了一个 urls.py 文件,并且绑定了 URL 和视图函数index
,但是 Django 并不知道。Django 匹配 URL 模式是在 blogproject\ 目录(即 settings.py 文件所在的目录)的 urls.py 下的,所以我们要把 blog 应用下的 urls.py 文件包含到 blogproject\urls.py 里去,打开这个文件看到如下内容:
python blogproject/urls.py """ 一大段注释 """ from django.conf.urls import url from django.contrib import admin urlpatterns = [ url(r'^admin/', admin.site.urls), ]
修改成如下的形式:
python - from django.conf.urls import url + from django.conf.urls import url, include from django.contrib import admin urlpatterns = [ url(r'^admin/', admin.site.urls), + url(r'', include('blog.urls')), ]
这里 - 表示删掉这一行,+ 表示添加这一行。
我们这里导入了一个 include
函数,然后利用这个函数把 blog 应用下的 urls.py 文件包含了进来。此外 include 前还有一个 r''
,这是一个空字符串。这里也可以写其它字符串,Django 会把这个字符串和后面 include 的 urls.py 文件中的 URL 拼接。比如说如果我们这里把 r''
改成 r'blog/'
,而我们在 blog.urls 中写的 URL 是 r'^$'
,即一个空字符串。那么 Django 最终匹配的就是 blog/ 加上一个空字符串,即 blog/。
运行结果
激活虚拟环境,运行 python manage.py runserver
打开开发服务器,在浏览器输入开发服务器的地址 http://127.0.0.1:8000/,可以看到 Django 返回的内容了。
欢迎访问我的博客首页!
使用 Django 模板系统
这基本上就上 Django 的开发流程了,写好处理 HTTP 请求和返回 HTTP 响应的视图函数,然后把视图函数绑定到相应的 URL 上。
但是等一等!我们看到在视图函数里返回的是一个 HttpResponse
类的实例,我们给它传入了一个希望显示在用户浏览器上的字符串。但是我们的博客不可能只显示这么一句话,它有可能会显示很长很长的内容。比如我们发布的博客文章列表,或者一大段的博客文章。我们不能每次都把这些大段大段的内容传给 HttpResponse
。
Django 对这个问题给我们提供了一个很好的解决方案,叫做模板系统。Django 要我们把大段的文本写到一个文件里,然后 Django 自己会去读取这个文件,再把读取到的内容传给 HttpResponse
。让我们用模板系统来改造一下上面的例子。
首先在我们的项目根目录(即 manage.py 文件所在目录)下建立一个名为 templates 的文件夹,用来存放我们的模板。然后在 templates\ 目录下建立一个名为 blog 的文件夹,用来存放和 blog 应用相关的模板。
当然模板存放在哪里是无关紧要的,只要 Django 能够找到的就好。但是我们建立这样的文件夹结构的目的是把不同应用用到的模板隔离开来,这样方便以后维护。我们在 templates\blog 目录下建立一个名为 index.html 的文件,
此时你的目录结构应该是这样的:
blogproject\ manage.py blogproject\ __init__.py settings.py ... blog\ __init__.py models.py ,,, templates\ blog\ index.html
再一次强调 templates\ 目录位于项目根目录,而 index.html 位于 templates\blog 目录下,而不是 blog 应用下,如果弄错了你可能会得到一个TemplateDoesNotExist 异常。如果遇到这个异常,请回来检查一下模板目录结构是否正确。
在 templates\blog\index.html 文件里写入下面的代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{ title }}</title>
</head>
<body>
<h1>{{ welcome }}</h1>
</body>
</html>
这是一个标准的 HTML 文档,只是里面有两个比较奇怪的地方:{{ title }}
,{{ welcome }}
。这是 Django 规定的语法。用 {{ }} 包起来的变量叫做模板变量。Django 在渲染这个模板的时候会根据我们传递给模板的变量替换掉这些变量。最终在模板中显示的将会是我们传递的值。
注意:index.html 必须以 UTF-8 的编码格式保存,且小心不要往里面添加一些特殊字符,否则极有可能得到一个 UnicodeDecodeError 这样的错误。
模板写好了,还得告诉 Django 去哪里找模板,在 settings.py 文件里设置一下模板文件所在的路径。在 settings.py 找到 TEMPLATES
选项,
它的内容是这样的:
pythonblogproject/settings.py
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
其中 DIRS
就是设置模板的路径,在 [] 中写入os.path.join(BASE_DIR, 'templates')
,即像下面这样:
blogproject/settings.py
TEMPLATES = [
{
...
'DIRS': [os.path.join(BASE_DIR, 'templates')],
...
},
]
这里 BASE_DIR
是 settings.py 在配置开头前面定义的变量,记录的是工程根目录 blogproject\ 的值(注意是最外层的 blogproject\ 目录)。在这个目录下有模板文件所在的目录 templates\,于是利用os.path.join
把这两个路径连起来,构成完整的模板路径,Django 就知道去这个路径下面找我们的模板了。
视图函数可以改一下了:
pythonblog/views.py
from django.http import HttpResponse
from django.shortcuts import render
def index(request):
return render(request, 'blog/index.html', context={
'title': '我的博客首页',
'welcome': '欢迎访问我的博客首页'
})
这里我们不再是直接把字符串传给 HttpResponse
了,而是调用 Django 提供的 render
函数。这个函数根据我们传入的参数来构造 HttpResponse
。
我们首先把 HTTP 请求传了进去,然后 render
根据第二个参数的值 blog/index.html 找到这个模板文件并读取模板中的内容。之后 render
根据我们传入的 context
参数的值把模板中的变量替换为我们传递的变量的值,{{ title }}
被替换成了 context
字典中 title
对应的值,同理 {{ welcome }}
也被替换成相应的值。
最终,我们的 HTML 模板中的内容字符串被传递给 HttpResponse
对象并返回给浏览器(Django 在 render
函数里隐式地帮我们完成了这个过程),这样用户的浏览器上便显示出了我们写的 HTML 模板的内容。
总结
本章节的代码位于:Step5: blog index view(https://github.com/zmrenwu/django-blog-tutorial/tree/Step5_blog-index-view)
如果遇到问题,请通过下面的方式寻求帮助。
在 Django 博客首页视图 - 追梦人物的博客(http://zmrenwu.com/post/7/) 的评论区留言。
将问题的详细描述通过邮件发送到 djangostudyteam@163.com,一般会在 24 小时内回复。
关注公众号,“Python爱好者社区”,回复“爬虫”即可获取崔老师爬虫免费学习视频。
为大家提供与Python相关的最新技术和资讯。
长按指纹 > 识别图中二维码 > 添加关注
“阅读原文”就是Django搭建的网站啦